using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.XPath;
using DebugCompanion.Engine;
using EnvDTE;

namespace DebugCompanion
{
	partial class ProjectSettingsDlg : Form
	{
		ProjectSettingsDlg()
		{
			InitializeComponent();
		}

		public static void Show(ProjectInfo info)
		{
			ProjectSettingsDlg dlg = new ProjectSettingsDlg();
			dlg._info = info;
			dlg._captionText.Text = info.Name;

			List<AssemblyInfo> assemblies = new List<AssemblyInfo>(FusionUtil.GetAssemblyFiles());
			assemblies.Sort(delegate(AssemblyInfo left, AssemblyInfo right)
							{
								return string.Compare(left.DisplayName, right.DisplayName);
							});
			foreach (AssemblyInfo file in assemblies)
			{
				dlg._itemsList.Items.Add(file);
			}

			dlg.ShowDialog();
		}

		private ProjectInfo _info;

		private void _enableDebugBtn_Click(object sender, EventArgs e)
		{
			try
			{
				List<AssemblyInfo> assemblies = AssemblyCacheController.Init(GetSelectedAssembliesList());
				if (assemblies == null || assemblies.Count == 0)
					return;

				DisableVsHostingProcess();
				if (!ConfigFileExists())
					CreateConfigFile();
				WriteRedirectionRules(assemblies);

				foreach (AssemblyInfo info in assemblies)
				{
					AssemblyProcessor.DisableStrongNameValidation(info.CodeBase);
				}
			}
			catch (Exception exc)
			{
				MessageBox.Show(exc.Message);
			}
		}

		void DisableVsHostingProcess()
		{
			foreach (Configuration config in _info.Project.ConfigurationManager)
			{
				if (config.Properties == null)
					continue;
				Property property =
					config.Properties.Item("UseVSHostingProcess");
				property.Value = false;
			}
			_info.Save();
		}

		string GetConfigName()
		{
			if (_info.ProjectType == ProjectInfo.WebProjectGuid 
				|| _info.ProjectType == ProjectInfo.WebPseudoProjectGuid)
				return "Web.config";
			else
				return "App.config";
		}

		string GetConfigFullName()
		{
			string res = Path.GetDirectoryName(_info.Project.FullName) + "\\" + GetConfigName();
			return res;
		}

		public bool ConfigFileExists()
		{
			bool configExists = false;
			foreach (ProjectItem item in _info.Project.ProjectItems)
			{
				if (string.Compare(item.Name, GetConfigName(), true) == 0)
				{
					configExists = true;
					break;
				}
			}
			return configExists;
		}

		void CreateConfigFile()
		{
			XmlDocument doc = new XmlDocument();
			doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null));
			doc.AppendChild(doc.CreateElement(null, "configuration", null));
			doc.Save(GetConfigFullName());
			_info.Project.ProjectItems.AddFromFile(GetConfigFullName());

			_info.Save();
		}

		List<AssemblyInfo> GetSelectedAssembliesList()
		{
			List<AssemblyInfo> res = new List<AssemblyInfo>(_itemsList.SelectedIndices.Count);
			foreach (object o in _itemsList.SelectedItems)
			{
				res.Add((AssemblyInfo)o);
			}
			return res;
		}

		static XmlElement EnsureNodeExists(XmlDocument doc, string path, XmlNamespaceManager nman)
		{
			string[] pathParts = path.Split('/');

			XmlElement elem = doc.DocumentElement;
			bool rootProcessed = false;
			foreach (string part in pathParts)
			{
				if (!rootProcessed)
				{
					if (!string.IsNullOrEmpty(part))
					{
						if (part != doc.DocumentElement.Name)
							throw new Exception("Root element name doesn't match");
						rootProcessed = true;
					}
					continue;
				}
				XmlNode node = elem.SelectSingleNode(part, nman);
				if (null == node)
				{
					if (part.Contains(":"))
					{
						string[] nameParts = part.Split(':');
						node = doc.CreateElement(nameParts[1], nman.LookupNamespace(nameParts[0]));
					}
					else
					{
						node = doc.CreateElement(part);
					}
					elem.AppendChild(node);
				}

				elem = (XmlElement)node;
			}

			return elem;
		}

		private const string _bindingNodePath = "/configuration/runtime/ms:assemblyBinding";

		static void CleanupOldRules(XmlDocument doc, XmlNamespaceManager nman)
		{
			XPathNavigator nav = doc.CreateNavigator();
			while (true)
			{
				XPathNodeIterator iter = nav.Select(_bindingNodePath, nman);
				if (!iter.MoveNext())
					break;
				iter.Current.DeleteSelf();
			}
		}

		void WriteRedirectionRules(List<AssemblyInfo> assemblies)
		{
			XmlDocument doc = new XmlDocument();
			XmlNamespaceManager nman = new XmlNamespaceManager(doc.NameTable);
			nman.AddNamespace("ms", "urn:schemas-microsoft-com:asm.v1");
			doc.Load(GetConfigFullName());
			CleanupOldRules(doc, nman);

			XmlElement bindingsNode = EnsureNodeExists(doc, _bindingNodePath, nman);

			foreach (AssemblyInfo info in assemblies)
			{
				Version newVersion = new Version(info.Version.Major, info.Version.Minor,
					info.Version.Build, info.Version.Revision + 1);
				string asmPath = EnvironmentUtil.GetAsmCachePath(info) + "\\" + info.Name;

				XmlElement dependencyNode = doc.CreateElement("dependentAssembly", bindingsNode.NamespaceURI);
				XmlElement identityNode = doc.CreateElement("assemblyIdentity", bindingsNode.NamespaceURI);
				identityNode.SetAttribute("name", Path.GetFileNameWithoutExtension(info.Name));
				identityNode.SetAttribute("publicKeyToken", info.PublicKeyToken);
				dependencyNode.AppendChild(identityNode);

				XmlElement publisherPolicyNode = doc.CreateElement("publisherPolicy", bindingsNode.NamespaceURI);
				publisherPolicyNode.SetAttribute("apply", "no");
				dependencyNode.AppendChild(publisherPolicyNode);

				XmlElement redirectNode = doc.CreateElement("bindingRedirect", bindingsNode.NamespaceURI);
				redirectNode.SetAttribute("oldVersion", info.Version.ToString());
				redirectNode.SetAttribute("newVersion", newVersion.ToString());
				dependencyNode.AppendChild(redirectNode);
				XmlElement codebaseNode = doc.CreateElement("codeBase", bindingsNode.NamespaceURI);
				codebaseNode.SetAttribute("version", newVersion.ToString());
				codebaseNode.SetAttribute("href", asmPath);
				dependencyNode.AppendChild(codebaseNode);

				bindingsNode.AppendChild(dependencyNode);
			}
			doc.Save(GetConfigFullName());
		}

		private void _disableDebugBtn_Click(object sender, EventArgs e)
		{
			XmlDocument doc = new XmlDocument();
			XmlNamespaceManager nman = new XmlNamespaceManager(doc.NameTable);
			nman.AddNamespace("ms", "urn:schemas-microsoft-com:asm.v1");
			doc.Load(GetConfigFullName());
			CleanupOldRules(doc, nman);
			doc.Save(GetConfigFullName());
		}
	}
}